﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using FrameworkExtensions;
using System.Dynamic;

namespace IOFtp
{
    public class FtpDirectory : FtpFileSystemObject
    {
        public FtpDirectory( Uri initialUri, NetworkCredential credentials )
            : base( initialUri, credentials )
        {
            FileInfo fi = new FileInfo( initialUri.AbsolutePath );
            if( fi.Name != String.Empty && fi.Extension != String.Empty )
                throw new Exception( String.Format( "Specified path '{0}' is not a directory", initialUri.ToString() ) );
        }

        public FtpDirectory( string initialUri, NetworkCredential credentials )
            : this( new Uri( initialUri ), credentials ) { }

        /// <summary>
        /// Reference to the upper level directory
        /// </summary>
        public FtpDirectory UpperLevel
        {
            get
            {
                if( this.CurrentDirectory.Segments.Length > 1 )
                {
                    var tmp = this.CurrentDirectory.ToString().LastIndexOf( this.CurrentDirectory.Segments.Last() );
                    var upperLevelUri = this.CurrentDirectory.ToString().Remove( tmp );

                    return new FtpDirectory( upperLevelUri, this.Credentials );
                }

                return null;
            }
        }

        #region SubDirectories

        public IEnumerable<FtpDirectory> SubDirectories
        {
            get
            {
                foreach( var listingDetail in this.GetDirectoryListDetails() )
                {
                    var rights = listingDetail.Substring( 0, 10 );
                    var perms = new PermissionAttributes( rights );

                    if( perms.IsDirectory )
                    {
                        string dirName = ListingDetailsParser.ExtractObjectName( listingDetail );

                        if( dirName != "." && dirName != ".." )
                        {
                            Uri newUri = this.CurrentDirectory.Combine( dirName );

                            yield return new FtpDirectory( newUri, this.Credentials )
                            {
                                Permissions = new PermissionAttributes( rights ),
                                LastModified = ListingDetailsParser.ExtractDate( listingDetail )
                            };
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Get all subdirectories from all sublevels starting from current directory
        /// </summary>
        public IEnumerable<FtpDirectory> GetAllSubDirectories()
        {
            return GetAllSubDirectories( this );
        }

        private IEnumerable<FtpDirectory> GetAllSubDirectories( FtpDirectory dir )
        {
            foreach( var subDir in dir.SubDirectories )
            {
                yield return new FtpDirectory( subDir.FullPath, this.Credentials );

                foreach( var subSubDir in this.GetAllSubDirectories( subDir ) )
                    yield return subSubDir;
            }
        }

        #endregion

        #region Files

        public IEnumerable<FtpFile> Files
        {
            get
            {
                foreach( var listingDetail in this.GetDirectoryListDetails() )
                {
                    var rights = listingDetail.Substring( 0, 10 );
                    var perms = new PermissionAttributes( rights );

                    if( !perms.IsDirectory )
                    {
                        string fileName = ListingDetailsParser.ExtractObjectName( listingDetail );

                        Uri newUri = this.CurrentDirectory.Combine( fileName );

                        yield return new FtpFile( newUri, this.Credentials )
                        {
                            Permissions = new PermissionAttributes( rights ),
                            LastModified = ListingDetailsParser.ExtractDate( listingDetail )
                        };
                    }
                }
            }
        }

        /// <summary>
        /// Get all files from all subdirectories starting from current directory
        /// </summary>
        public IEnumerable<FtpFile> GetAllFiles()
        {
            return GetAllFiles( this );
        }

        private IEnumerable<FtpFile> GetAllFiles( FtpDirectory ftp )
        {
            foreach( var subDir in ftp.SubDirectories )
            {
                foreach( var file in subDir.Files )
                    yield return file;

                foreach( var file in this.GetAllFiles( subDir ) )
                    yield return file;
            }
        }

        #endregion

        #region Download

        public override void Download( string destination )
        {
            this.Download( destination );
        }

        public void Download( string destination, string regexSearchPattern = ".*", SearchOption searchOption = SearchOption.TopDirectoryOnly )
        {
            Directory.CreateDirectory( destination );

            foreach( var file in this.Files )
            {
                if( Regex.IsMatch( file.Name, regexSearchPattern ) )
                    file.Download( destination );
            }

            if( searchOption == SearchOption.AllDirectories )
            {
                foreach( var subdir in this.SubDirectories )
                    subdir.Download( Path.Combine( destination, subdir.Name ), regexSearchPattern, searchOption );
            }
        }

        public IEnumerable<FtpFile> DownloadAndGetFtpReference( string destination, string regexSearchPattern = ".*", SearchOption searchOption = SearchOption.TopDirectoryOnly )
        {
            Directory.CreateDirectory( destination );

            foreach( var file in this.Files )
            {
                if( Regex.IsMatch( file.Name, regexSearchPattern ) )
                {
                    file.Download( destination );
                    yield return file;
                }
            }

            if( searchOption == SearchOption.AllDirectories )
            {
                foreach( var subdir in this.SubDirectories )
                {
                    var files = subdir.DownloadAndGetFtpReference( Path.Combine( destination, subdir.Name ), regexSearchPattern, searchOption );
                    foreach( var file in files )
                        yield return file;
                }
            }
        }

        #endregion

        #region Upload

        public void UploadDirectory( string localDir )
        {
            var files = Directory.GetFiles( localDir, "*.*", SearchOption.TopDirectoryOnly );
            foreach( var file in files )
                this.UploadFile( file );

            var dirs = Directory.GetDirectories( localDir, "*.*", SearchOption.TopDirectoryOnly );
            foreach( var dir in dirs )
            {
                var dirInfo = new DirectoryInfo( dir );

                var newFtpDir = this.CreateSubDirectory( dirInfo.Name );
                newFtpDir.UploadDirectory( dir );
            }
        }

        public FtpFile UploadFile( string localFile )
        {
            string localFileName = Path.GetFileName( localFile );
            string remoteFile = UriExtensions.Combine( this.FullPath.ToString(), localFileName ).ToString();

            this.WebClient.UploadFile( remoteFile, localFile );

            return new FtpFile( remoteFile, this.Credentials );
        }

        #endregion

        #region Delete

        /// <summary>
        /// Removes all files and subdirectories from current directory
        /// </summary>
        public override void Delete()
        {
            foreach( var file in this.Files )
                file.Delete();

            foreach( var subdir in this.SubDirectories )
                subdir.Delete();

            this.Request = (FtpWebRequest)WebRequest.Create( this.CurrentDirectory );
            this.Request.Credentials = this.Credentials;
            this.Request.Method = WebRequestMethods.Ftp.RemoveDirectory;

            FtpWebResponse response = (FtpWebResponse)this.Request.GetResponse();
        }

        #endregion

        /// <summary>
        /// Creates all directories in specified path as subdirectories.
        /// If directory already exists retuns a reference to that directory.
        /// </summary>
        /// <example> \sub1\sub2\sub3</example>
        /// <returns>Returns a reference to the new directory</returns>
        public FtpDirectory CreateSubDirectory( string path )
        {
            // 1. test if final path exists: if so return a reference to it
            string finalPath = Path.Combine( this.FullPath.ToString(), path ).Replace( @"\", @"/" );

            if( !this.GetAllSubDirectories().Any( dir => dir.FullPath == finalPath ) )
            {
                // 2. if final path do not exists create each subdirectory
                var subDirs = path.Replace( @"\", @"/" ).Split( new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries );

                var newDir = this.FullPath.ToString();
                foreach( var subDir in subDirs )
                {
                    newDir = Path.Combine( newDir, subDir ).Replace( @"\", @"/" );

                    if( !this.GetAllSubDirectories().Any( dir => dir.FullPath == newDir ) )
                    {
                        var request = (FtpWebRequest)WebRequest.Create( newDir );
                        request.Credentials = this.Credentials;
                        request.Method = WebRequestMethods.Ftp.MakeDirectory;
                        request.GetResponse();
                    }
                }
            }

            return new FtpDirectory( finalPath, this.Credentials );
        }

        public FtpDirectory ChangeDirectory( string path )
        {
            string finalPath = Path.Combine( this.FullPath.ToString(), path ).Replace( @"\", @"/" );

            if( this.GetAllSubDirectories().Any( dir => dir.FullPath == finalPath ) )
                return new FtpDirectory( finalPath, this.Credentials );

            throw new DirectoryNotFoundException( String.Format( "Could not find '{0}'", finalPath ) );
        }

        public override bool TryGetMember( GetMemberBinder binder, out object result )
        {
            result = this.ChangeDirectory( binder.Name );
            return true;
        }
    }
}